2. Tratamiento de correlaciones, valores missing y outliers¶

Valores missing, outlier y correlaciones¶

En este notebook se realiza el estudio y preprocesamiento de las variables numéricas y categoricas. Se realizarán los siguientes pasos:

  1. Cambio de tipos de variables
  2. Separación en train y test
  3. Análisis de cada variable con gráficos descriptivos
  4. Para variables numericas: correlaciones de pearnson, estudio de outliers y estudio de valores missing
  5. Para variables categoricas: relleno de valores missing, estudio de correlaciones con vCramer
In [1]:
# Importación de la biblioteca IPython.display para mostrar imágenes.
from IPython.display import Image
In [2]:
Image(filename="../Practica_Eda_Miguel_Garcia/images/analisis_outlier.png")
Out[2]:
No description has been provided for this image

Vemos que son los valores outliers y como afectan a los datos

In [3]:
Image(filename="../Practica_Eda_Miguel_Garcia/images/Missing_data.png")
Out[3]:
No description has been provided for this image

Estas son las diferentes maneras que existen para administrar los valores missing

Importo librerías¶

In [4]:
import pandas as pd
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt
import plotly.express as px
from sklearn.impute import KNNImputer
import scipy.stats as ss
import warnings

pd.set_option('display.max_columns', 500)
pd.set_option('display.max_rows', 5000)
warnings.filterwarnings('ignore')

Funciones¶

Buenas prácticas

Voy guardando las funciones que están automatizadas y pienso que me van a servir en otros proyectos en un funciones_auxiliares.py y lo importo:

In [5]:
import funciones_auxiliares as f_aux

Lectura de datos del preprocesado inicial¶

Lectura de los datos y cambio de tipos de variables

In [6]:
import pandas as pd

data = pd.read_csv("../Practica_Eda_Miguel_Garcia/data/df_initial_preprocessing.csv").drop('Unnamed: 0', axis=1)
In [7]:
list_var_cat, other = f_aux.dame_variables_categoricas(dataset=data)
data[list_var_cat] = data[list_var_cat].astype("category")
list_var_continuous = list(data.select_dtypes('float').columns)
data[list_var_continuous] = data[list_var_continuous].astype(float)
data.dtypes
Out[7]:
intended_balcon_amount               float64
prev_address_months_count            float64
bank_months_count                    float64
current_address_months_count         float64
session_length_in_minutes            float64
device_distinct_emails_8w            float64
fraud_bool                          category
foreign_request                      float64
phone_mobile_valid                   float64
has_other_cards                      float64
proposed_credit_limit                float64
device_os                           category
source                              category
housing_status                      category
keep_alive_session                   float64
device_fraud_count                   float64
phone_home_valid                     float64
credit_risk_score                    float64
email_is_free                        float64
income                               float64
employment_status                   category
date_of_birth_distinct_emails_4w     float64
bank_branch_count_8w                 float64
velocity_4w                          float64
velocity_24h                         float64
velocity_6h                          float64
zip_count_4w                         float64
payment_type                        category
days_since_request                   float64
customer_age                         float64
name_email_similarity                float64
month                                float64
dtype: object

Separación en train y test estratificado¶

In [8]:
data_status = data['fraud_bool']\
        .value_counts(normalize=True)\
        .mul(100).rename('percent').reset_index()

data_status_conteo = data['fraud_bool'].value_counts().reset_index()
data_status_pc = pd.merge(data_status, 
                                  data_status_conteo, on=['fraud_bool'], how='inner')

fig = px.histogram(data_status_pc, x="fraud_bool", y=['percent'])
fig.show()
In [9]:
from sklearn.model_selection import train_test_split

X_data, X_data_test, y_data, y_data_test = train_test_split(
    data.drop('fraud_bool', axis=1),  
    data['fraud_bool'],                
    stratify=data['fraud_bool'],       
    test_size=0.2                      
)

data_train = pd.concat([X_data, y_data], axis=1)
data_test = pd.concat([X_data_test, y_data_test], axis=1)
In [10]:
print('== Train\n', data_train['fraud_bool'].value_counts(normalize=True))
print('== Test\n', data_test['fraud_bool'].value_counts(normalize=True))
== Train
 fraud_bool
0    0.988971
1    0.011029
Name: proportion, dtype: float64
== Test
 fraud_bool
0    0.98897
1    0.01103
Name: proportion, dtype: float64
  • Dividimos el conjunto de datos en train y test,dejando a train con un 20 % de los datos
  • Asimismo comprobamos que la division de los valores entre Train y Test a resultado equitativa y que representa a todo el dataset

Visualización descriptiva de los datos¶

Veo el número de valores nulos por filas y por columnas, como sustituimos los nulos en el notebook anterior(antes eran todos aquellos menores de 0)

In [11]:
data_series_null_columns = data.isnull().sum().sort_values(ascending=False)
data_series_null_rows = data.isnull().sum(axis=1).sort_values(ascending=False)
print(data_series_null_columns.shape, data_series_null_rows.shape)

data_null_columnas = pd.DataFrame(data_series_null_columns, columns=['nulos_columnas'])     
data_null_filas = pd.DataFrame(data_series_null_rows, columns=['nulos_filas'])  
data_null_filas['target'] = data['fraud_bool'].copy()
data_null_columnas['porcentaje_columnas'] = data_null_columnas['nulos_columnas']/data.shape[0]
data_null_filas['porcentaje_filas'] = data_null_filas['nulos_filas']/data.shape[1]
(32,) (1000000,)
In [12]:
data_null_columnas
Out[12]:
nulos_columnas porcentaje_columnas
intended_balcon_amount 742523 0.742523
prev_address_months_count 712920 0.712920
bank_months_count 253635 0.253635
current_address_months_count 4254 0.004254
session_length_in_minutes 2015 0.002015
device_distinct_emails_8w 359 0.000359
velocity_6h 0 0.000000
date_of_birth_distinct_emails_4w 0 0.000000
bank_branch_count_8w 0 0.000000
velocity_4w 0 0.000000
velocity_24h 0 0.000000
payment_type 0 0.000000
zip_count_4w 0 0.000000
income 0 0.000000
days_since_request 0 0.000000
customer_age 0 0.000000
name_email_similarity 0 0.000000
employment_status 0 0.000000
phone_home_valid 0 0.000000
email_is_free 0 0.000000
credit_risk_score 0 0.000000
device_fraud_count 0 0.000000
keep_alive_session 0 0.000000
housing_status 0 0.000000
source 0 0.000000
device_os 0 0.000000
proposed_credit_limit 0 0.000000
has_other_cards 0 0.000000
phone_mobile_valid 0 0.000000
foreign_request 0 0.000000
fraud_bool 0 0.000000
month 0 0.000000
In [13]:
data_null_filas.head()
Out[13]:
nulos_filas target porcentaje_filas
415911 5 0 0.15625
539043 5 0 0.15625
315076 5 0 0.15625
980028 5 0 0.15625
281065 5 0 0.15625
In [14]:
columnas_numericas = data_train.select_dtypes(include=[np.number])
variables_numericas = columnas_numericas.columns.tolist()
print(variables_numericas)
['intended_balcon_amount', 'prev_address_months_count', 'bank_months_count', 'current_address_months_count', 'session_length_in_minutes', 'device_distinct_emails_8w', 'foreign_request', 'phone_mobile_valid', 'has_other_cards', 'proposed_credit_limit', 'keep_alive_session', 'device_fraud_count', 'phone_home_valid', 'credit_risk_score', 'email_is_free', 'income', 'date_of_birth_distinct_emails_4w', 'bank_branch_count_8w', 'velocity_4w', 'velocity_24h', 'velocity_6h', 'zip_count_4w', 'days_since_request', 'customer_age', 'name_email_similarity', 'month']

Creamos una lista con las variables numéricas, para agilizar la realización de los gráficos

In [15]:
import warnings
warnings.filterwarnings('ignore')
#for i in list(bf_train.columns):
for i in variables_numericas:
    f_aux.plot_feature(data_train, col_name=i, isContinuous=True, target='fraud_bool')
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Estos gráficos nos dan informacion detallada sobre cada una de las variables, dividiendolas en numéricas y categóricas, siendo las numéricas las azules. Asimismo comparamos algunas variables con nuestra variable objetivo, como es la "velocity_24" para comparar ambas variables

Tratamiento de las variables continuas¶

A continuación, se tratan los valores missing, las correlaciones de las vairbales continuas y los outlier

In [16]:
variables_numericas
Out[16]:
['intended_balcon_amount',
 'prev_address_months_count',
 'bank_months_count',
 'current_address_months_count',
 'session_length_in_minutes',
 'device_distinct_emails_8w',
 'foreign_request',
 'phone_mobile_valid',
 'has_other_cards',
 'proposed_credit_limit',
 'keep_alive_session',
 'device_fraud_count',
 'phone_home_valid',
 'credit_risk_score',
 'email_is_free',
 'income',
 'date_of_birth_distinct_emails_4w',
 'bank_branch_count_8w',
 'velocity_4w',
 'velocity_24h',
 'velocity_6h',
 'zip_count_4w',
 'days_since_request',
 'customer_age',
 'name_email_similarity',
 'month']

Tratamiento de outliers¶

In [17]:
f_aux.get_deviation_of_mean_perc(data_train, list_var_continuous, target='fraud_bool', multiplier=3)
Out[17]:
variable sum_outlier_values target_value percentage
0 intended_balcon_amount 1461 0 0.989733
1 prev_address_months_count 6938 0 0.993658
2 current_address_months_count 17022 0 0.984197
3 session_length_in_minutes 18844 0 0.979728
4 device_distinct_emails_8w 25369 0 0.962237
5 foreign_request 20250 0 0.978222
6 proposed_credit_limit 4970 0 0.867203
7 credit_risk_score 2899 0 0.962746
8 date_of_birth_distinct_emails_4w 4955 0 0.993744
9 bank_branch_count_8w 32743 0 0.989830
10 velocity_24h 446 0 0.997758
11 velocity_6h 3505 0 0.993153
12 zip_count_4w 12990 0 0.990069
13 days_since_request 14177 0 0.987515
14 customer_age 6300 0 0.959206

Vemos que los valores outlier no son demasiado significativos dentro del dataset, el valor "proposed_credit_limit" es aquel que en teoría tiene mas importancia para detectar el fraude, dado que cuenta con un 12% de valores en fraude, asimismo vemos que el valor "velocity_24h" es aquel que en teoría mas importancia tiene, dado que todos sus valores outlier se encuentran dentro de no fraude.

Con esta tabla podemos ver qué variables son mas comunes, durante un fraude

Correlaciones¶

In [18]:
f_aux.get_corr_matrix(dataset = data_train[list_var_continuous], 
                metodo='pearson', size_figure=[10,8])
No description has been provided for this image
Out[18]:
0

Este gráfico intenta ver la relación entre fraud_bool y otras variables numéricas continuas en el conjunto de datos.

  • Colores cercanos al verde: correlación positiva.
  • Colores Cercanos al morado: correlación negativa.
  • Las celdas sin color, nos dicen que no hay una correlación fuerte, ya sea positiva o negativa.

Los valores para fraud_bool parecen ser bajos, dado que la mayoría de las celdas en su fila y columna son oscuras, lo que sugiere que no hay muchas correlaciones fuertes entre fraud_bool y las otras variables.

Hay algunas celdas con un color ligeramente más claro o más oscuro en la fila y columna de fraud_bool, lo que indica algunas correlaciones moderadas.

In [19]:
# con esta funcion podemos sacar que variables se encuentran correlacionadas entre si
corr = data_train[list_var_continuous].corr('pearson')
new_corr = corr.abs()
new_corr.loc[:,:] = np.tril(new_corr, k=-1) # matriz triangular inferior, esto se coge con n.tril
new_corr = new_corr.stack().to_frame('correlation').reset_index().sort_values(by='correlation', ascending=False)
new_corr[new_corr['correlation']>0.6]# sacacmos aquellos valorees con una correlacion mayor a 0.6 osea las mas correlacionadas
Out[19]:
level_0 level_1 correlation
643 month velocity_4w 0.848311
335 credit_risk_score proposed_credit_limit 0.605631
Decido en esta primera iteración, no eliminar correlaciones. Pese a month presentar una correlacion superior al estandar del umbral de correlacin, el cual suele ser de 0,7 a 0,8 mas o menos y por lo tanto presentar mucha correlacion.¶

Tratamiento de valores nulos¶

In [20]:
list_var_continuous
Out[20]:
['intended_balcon_amount',
 'prev_address_months_count',
 'bank_months_count',
 'current_address_months_count',
 'session_length_in_minutes',
 'device_distinct_emails_8w',
 'foreign_request',
 'phone_mobile_valid',
 'has_other_cards',
 'proposed_credit_limit',
 'keep_alive_session',
 'device_fraud_count',
 'phone_home_valid',
 'credit_risk_score',
 'email_is_free',
 'income',
 'date_of_birth_distinct_emails_4w',
 'bank_branch_count_8w',
 'velocity_4w',
 'velocity_24h',
 'velocity_6h',
 'zip_count_4w',
 'days_since_request',
 'customer_age',
 'name_email_similarity',
 'month']
In [21]:
# con esto sacamos la suma de los valores nulos para la variable.
f_aux.get_percent_null_values_target(data_train, list_var_continuous, target='fraud_bool')
Out[21]:
no_fraud fraud variable sum_null_values porcentaje_sum_null_values
0 0.986899 0.013101 intended_balcon_amount 593828 0.742285
1 0.985784 0.014216 prev_address_months_count 570267 0.712834
2 0.983762 0.016238 bank_months_count 202677 0.253346
3 0.995924 0.004076 current_address_months_count 3435 0.004294
4 0.990752 0.009248 session_length_in_minutes 1622 0.002027
5 0.992806 0.007194 device_distinct_emails_8w 278 0.000347

Opción 0:¶

Algunos algoritmos aceptan en su input valores missing

Opción 1:¶

eliminar todas las filas que tengan valores nulos.

Opción 2:¶

Imputar los valores missing por:

  • media
  • mediana
  • maximo
  • minimo
  • valores extremos https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.fillna.html

Decido rellenar todas las columnas continuas menos 'fraud_bool' por el valor -99. De esta manera, diferencio los outlier del resto de la muestra poninendo un valor muy separado del resto de la variable. Se puede explorar el resultado del modelo utilizando diferentes métodos

In [22]:
list_vars = list(set(list_var_continuous)-set(['fraud_bool']))
data_train[list_vars] = data_train[list_vars].fillna(-99)
data_test[list_vars] = data_test[list_vars].fillna(-99)
In [23]:
list_vars
Out[23]:
['zip_count_4w',
 'velocity_6h',
 'device_distinct_emails_8w',
 'days_since_request',
 'velocity_4w',
 'has_other_cards',
 'session_length_in_minutes',
 'phone_mobile_valid',
 'date_of_birth_distinct_emails_4w',
 'current_address_months_count',
 'prev_address_months_count',
 'customer_age',
 'velocity_24h',
 'intended_balcon_amount',
 'month',
 'proposed_credit_limit',
 'email_is_free',
 'device_fraud_count',
 'bank_branch_count_8w',
 'keep_alive_session',
 'credit_risk_score',
 'bank_months_count',
 'phone_home_valid',
 'income',
 'name_email_similarity',
 'foreign_request']
In [24]:
data_test['fraud_bool'].isnull().sum()
Out[24]:
0
In [25]:
f_aux.get_percent_null_values_target(data_test, list_var_continuous, target='fraud_bool')
No existen variables con valores nulos

Opción 3:¶

  • https://scikit-learn.org/stable/modules/impute.html. Utilizar un modelo de regresión para rellenar los valores mmissing de alguna variable muy importante, por ejemplo: KNN, regresion lineal, xgboost. Pero, cuidado con el sobreajuste.

Vamos a usar KNNImputer para imputar los valores missing de la variable fraus_bool usando como regresoras todas las variables continuas

In [26]:
# aqui no tratamos lo valores outliers
X_train = data_train[list(set(list_var_continuous))]
X_test = data_test[list(set(list_var_continuous))]
imputer = KNNImputer(n_neighbors=2, weights="uniform") 
model = imputer.fit(X_train)
pd_input_train = pd.DataFrame(model.transform(X_train), 
                      columns=[i+'_input' for i in list(set(list_var_continuous))],index=data_train.index)
pd_input_test = pd.DataFrame(model.transform(X_test), 
                      columns=[i+'_input' for i in list(set(list_var_continuous))],index=data_test.index)

data_input_train = pd.concat([data_train, pd_input_train],axis=1).drop(list(set(list_var_continuous)),axis=1)
data_input_test = pd.concat([data_test, pd_input_test],axis=1).drop(list(set(list_var_continuous)),axis=1)
In [27]:
data_input_train.shape
Out[27]:
(800000, 32)
In [28]:
f_aux.get_percent_null_values_target(data_input_train, [i+'_input' for i in list_var_continuous], target='fraud_bool')
No existen variables con valores nulos
In [31]:
list_var_continuous = list(data_input_train.select_dtypes('float').columns)
f_aux.get_corr_matrix(dataset = data_input_train[list_var_continuous], 
                metodo='pearson', size_figure=[10,8])
No description has been provided for this image
Out[31]:
0

Este gráfico de Pearson, mide la relación lineal entre pares de variables. La escala de colores va de -0.8 a 0.6.

  • Colores cercanos al verde: correlación positiva.
  • Colores Cercanos al morado: correlación negativa.
  • Las celdas sin color, nos dicen que no hay una correlación fuerte, ya sea positiva o negativa.

Podemos ver algunos casos de correlación muy negativa de las variables, sobre todo en la parte izquierda del gráfico por lo tanto estas cuantan con fuerte correlación negativa con la variable

In [32]:
data_input_train.columns
Out[32]:
Index(['device_os', 'source', 'housing_status', 'employment_status',
       'payment_type', 'fraud_bool', 'velocity_6h_input',
       'device_distinct_emails_8w_input', 'days_since_request_input',
       'velocity_4w_input', 'prev_address_months_count_input',
       'intended_balcon_amount_input', 'month_input', 'email_is_free_input',
       'bank_branch_count_8w_input', 'keep_alive_session_input',
       'credit_risk_score_input', 'bank_months_count_input',
       'phone_home_valid_input', 'income_input', 'foreign_request_input',
       'zip_count_4w_input', 'has_other_cards_input',
       'session_length_in_minutes_input', 'phone_mobile_valid_input',
       'date_of_birth_distinct_emails_4w_input',
       'current_address_months_count_input', 'customer_age_input',
       'velocity_24h_input', 'proposed_credit_limit_input',
       'device_fraud_count_input', 'name_email_similarity_input'],
      dtype='object')

Tratamiento de las variables categoricas¶

Para la correlacion de spearman es necesario convertir las variables categoricas en numericas y luego obtener la correlación

También está el coeficiente V-Cramer https://stackoverflow.com/questions/46498455/categorical-features-correlation

In [33]:
list_var_cat
Out[33]:
['fraud_bool',
 'device_os',
 'source',
 'housing_status',
 'employment_status',
 'payment_type']
In [34]:
confusion_matrix = pd.crosstab(data_input_train["payment_type"], data_input_train["employment_status"])
print(confusion_matrix)
f_aux.cramers_v(confusion_matrix.values)
employment_status      CA     CB     CC    CD    CE     CF   CG
payment_type                                                   
AA                 149932  33588   5719  4133  7062   6324  133
AB                 215312  38674  13929  7580  5542  15182  116
AC                 149864  21368   8063  7636  3646  10730   78
AD                  68941  16899   2435  1883  1942   3011   42
AE                    173     31      6     8     6     11    1
Out[34]:
0.05673728868605832

Con la variabe crosstab podemos ver la correlacion de pearson de las variables, en este caso de payment_type y de employment_status, dado que son unsa de las mas importantes

In [35]:
confusion_matrix = pd.crosstab(data_input_train["payment_type"], data_input_train["payment_type"])
f_aux.cramers_v(confusion_matrix.values)
Out[35]:
0.9999999999999999
In [36]:
confusion_matrix = pd.crosstab(data_input_train["payment_type"], data_input_train["housing_status"])
f_aux.cramers_v(confusion_matrix.values)
Out[36]:
0.09821978810919921

Tratamiento de valores nulos¶

En las variables categoricas, los valores nulos se suelen sustituir por una nueva clase: "sin valor" o por la moda

Como ya hemos mencionado, en el notebook anterior, en nuestro dataset, los valores -1 son los equivalentes a los nulos

In [37]:
data_input_train[list_var_cat] = data_input_train[list_var_cat].astype("object").fillna("SIN VALOR").astype("category")
data_input_test[list_var_cat] = data_input_test[list_var_cat].astype("object").fillna("SIN VALOR").astype("category")

guardamos lo valores¶

In [38]:
data_input_train.to_csv("../Practica_Eda_Miguel_Garcia/data/train_data_preprocessing_missing_outlier.csv")
data_input_test.to_csv("../Practica_Eda_Miguel_Garcia/data/test_data_preprocessing_missing_outlier.csv")
In [39]:
print(data_input_train.shape, data_input_test.shape)
data_input_test.dtypes
(800000, 32) (200000, 32)
Out[39]:
device_os                                 category
source                                    category
housing_status                            category
employment_status                         category
payment_type                              category
fraud_bool                                category
velocity_6h_input                          float64
device_distinct_emails_8w_input            float64
days_since_request_input                   float64
velocity_4w_input                          float64
prev_address_months_count_input            float64
intended_balcon_amount_input               float64
month_input                                float64
email_is_free_input                        float64
bank_branch_count_8w_input                 float64
keep_alive_session_input                   float64
credit_risk_score_input                    float64
bank_months_count_input                    float64
phone_home_valid_input                     float64
income_input                               float64
foreign_request_input                      float64
zip_count_4w_input                         float64
has_other_cards_input                      float64
session_length_in_minutes_input            float64
phone_mobile_valid_input                   float64
date_of_birth_distinct_emails_4w_input     float64
current_address_months_count_input         float64
customer_age_input                         float64
velocity_24h_input                         float64
proposed_credit_limit_input                float64
device_fraud_count_input                   float64
name_email_similarity_input                float64
dtype: object

Conclusión¶

Primeramente separamos en test y train, con un 20% para test,acto seguido, preparamos los datos para su visualización de las variables usando una funcion de func-aux, estos gráficos nos dan informacion detallada de cada una de las variables que encontramos actualmente en el dataset.

A continuación tratamos los outliers y vemos las correlaciones existentes entre las diferentes variables del dataset. Nos dan varias opciones para realizar el tratamiento de los valores nulos, nosotros elegimos la Opción tres, dadoq eu es la mas recomendable(Utilizar un modelo de regresión para rellenar los valores mmissing de alguna variable muy importante).

Finalmente vemos la relación existente con las variables categóricas, donde podemos ver como se relacionan entre ellas mismas.